package main

import (
	"net"
	"syscall"
)

type TCP struct {
	Stream
}

func (this *TCP) Init(l *Loop) error {
	return this.InitEx(l, syscall.AF_UNSPEC)
}

func (this *TCP) InitEx(l *Loop, flags uint32) error {
	domain := flags & 0xFF
	switch domain {
	case syscall.AF_INET, syscall.AF_INET6, syscall.AF_UNSPEC:
	default:
		return syscall.EINVAL
	}
	if ISet(flags, ^uint32(0xFF)) {
		return syscall.EINVAL
	}
	this.Stream.Init(l, HandleTypeTCP)
	return nil
}

func (this *TCP) Listen(network, address string, flags uint32, cb ConnectionCallback) error {
	laddr, err := net.ResolveTCPAddr(network, address)
	if err != nil {
		return err
	}

	family := syscall.AF_INET
	if network[len(network)-1] == '6' {
		family = syscall.AF_INET6
	}

	fd, err := syscall.Socket(family, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK, 0)
	if err != nil {
		return err
	}

	sa, err := ipToSockaddr(family, laddr.IP, laddr.Port, laddr.Zone)
	if err != nil {
		return err
	}

	err = syscall.Bind(fd, sa)
	if err != nil {
		syscall.Close(fd)
		return err
	}

	err = syscall.Listen(fd, 128)
	if err != nil {
		syscall.Close(fd)
		return err
	}

	this.connectionCallback = cb
	this.Flags |= HandleStatusBound

	/* start listening for connections */
	this.io.fd = fd
	this.io.cb = this.serverIO
	this.io.start(this.Loop, syscall.EPOLLIN)

	return nil
}

func (this *TCP) serverIO(l *Loop, events uint32) {
	this.io.start(l, syscall.EPOLLIN)

	/* connection callback can close the server socket while we're
	 * in the loop so check it on each iteration.
	 */
	var (
		peerfd int
		err    error
	)

	for this.io.fd != -1 {
		peerfd, _, err = syscall.Accept4(this.io.fd, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
		if peerfd < 0 {
			if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
				return // not error, try next time
			}
			if err == syscall.ECONNABORTED {
				continue // retry
			}
			this.connectionCallback(&this.Stream, err)
		}

		this.acceptedFd = peerfd
		this.connectionCallback(&this.Stream, nil)

		if this.acceptedFd != -1 {
			/* The user hasn't yet accepted called accept() */
			this.io.stop(l, syscall.EPOLLIN)
			return
		}
	}
}
